"""Sensor platform for Miele integration."""

from __future__ import annotations

from collections.abc import Callable, Mapping
from dataclasses import dataclass
import logging
from typing import Any, Final, cast

from pymiele import MieleDevice, MieleTemperature

from homeassistant.components.sensor import (
    RestoreSensor,
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.const import (
    PERCENTAGE,
    REVOLUTIONS_PER_MINUTE,
    STATE_UNKNOWN,
    EntityCategory,
    UnitOfEnergy,
    UnitOfTemperature,
    UnitOfTime,
    UnitOfVolume,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType

from .const import (
    COFFEE_SYSTEM_PROFILE,
    DISABLED_TEMP_ENTITIES,
    DOMAIN,
    PROGRAM_PHASE,
    STATE_PROGRAM_ID,
    STATE_STATUS_TAGS,
    MieleAppliance,
    PlatePowerStep,
    StateDryingStep,
    StateProgramType,
    StateStatus,
)
from .coordinator import MieleConfigEntry, MieleDataUpdateCoordinator
from .entity import MieleEntity

PARALLEL_UPDATES = 0

_LOGGER = logging.getLogger(__name__)

DEFAULT_PLATE_COUNT = 4

PLATE_COUNT = {
    "KM7575": 6,
    "KM7678": 6,
    "KM7697": 6,
    "KM7878": 6,
    "KM7897": 6,
    "KMDA7633": 5,
    "KMDA7634": 5,
    "KMDA7774": 5,
    "KMX": 6,
}

ATTRIBUTE_PROFILE = "profile"


def _get_plate_count(tech_type: str) -> int:
    """Get number of zones for hob."""
    stripped = tech_type.replace(" ", "")
    for prefix, plates in PLATE_COUNT.items():
        if stripped.startswith(prefix):
            return plates
    return DEFAULT_PLATE_COUNT


def _convert_duration(value_list: list[int]) -> int | None:
    """Convert duration to minutes."""
    return value_list[0] * 60 + value_list[1] if value_list else None


def _convert_temperature(
    value_list: list[MieleTemperature], index: int
) -> float | None:
    """Convert temperature object to readable value."""
    if index >= len(value_list):
        return None
    raw_value = cast(int, value_list[index].temperature) / 100.0
    if raw_value in DISABLED_TEMP_ENTITIES:
        return None
    return raw_value


def _get_coffee_profile(value: MieleDevice) -> str | None:
    """Get coffee profile from value."""
    if value.state_program_id is not None:
        for key_range, profile in COFFEE_SYSTEM_PROFILE.items():
            if value.state_program_id in key_range:
                return profile
    return None


@dataclass(frozen=True, kw_only=True)
class MieleSensorDescription(SensorEntityDescription):
    """Class describing Miele sensor entities."""

    value_fn: Callable[[MieleDevice], StateType]
    end_value_fn: Callable[[StateType], StateType] | None = None
    extra_attributes: dict[str, Callable[[MieleDevice], StateType]] | None = None
    zone: int | None = None
    unique_id_fn: Callable[[str, MieleSensorDescription], str] | None = None


@dataclass
class MieleSensorDefinition:
    """Class for defining sensor entities."""

    types: tuple[MieleAppliance, ...]
    description: MieleSensorDescription


SENSOR_TYPES: Final[tuple[MieleSensorDefinition, ...]] = (
    MieleSensorDefinition(
        types=(
            MieleAppliance.WASHING_MACHINE,
            MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
            MieleAppliance.TUMBLE_DRYER,
            MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
            MieleAppliance.DISHWASHER,
            MieleAppliance.OVEN,
            MieleAppliance.OVEN_MICROWAVE,
            MieleAppliance.HOB_HIGHLIGHT,
            MieleAppliance.STEAM_OVEN,
            MieleAppliance.MICROWAVE,
            MieleAppliance.COFFEE_SYSTEM,
            MieleAppliance.HOOD,
            MieleAppliance.FRIDGE,
            MieleAppliance.FREEZER,
            MieleAppliance.FRIDGE_FREEZER,
            MieleAppliance.ROBOT_VACUUM_CLEANER,
            MieleAppliance.WASHER_DRYER,
            MieleAppliance.DISH_WARMER,
            MieleAppliance.HOB_INDUCTION,
            MieleAppliance.STEAM_OVEN_COMBI,
            MieleAppliance.WINE_CABINET,
            MieleAppliance.WINE_CONDITIONING_UNIT,
            MieleAppliance.WINE_STORAGE_CONDITIONING_UNIT,
            MieleAppliance.STEAM_OVEN_MICRO,
            MieleAppliance.DIALOG_OVEN,
            MieleAppliance.WINE_CABINET_FREEZER,
            MieleAppliance.STEAM_OVEN_MK2,
            MieleAppliance.HOB_INDUCT_EXTR,
        ),
        description=MieleSensorDescription(
            key="state_status",
            translation_key="status",
            value_fn=lambda value: value.state_status,
            device_class=SensorDeviceClass.ENUM,
            options=sorted(set(STATE_STATUS_TAGS.values())),
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.WASHING_MACHINE,
            MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
            MieleAppliance.TUMBLE_DRYER,
            MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
            MieleAppliance.DISHWASHER,
            MieleAppliance.DISH_WARMER,
            MieleAppliance.OVEN,
            MieleAppliance.OVEN_MICROWAVE,
            MieleAppliance.STEAM_OVEN,
            MieleAppliance.MICROWAVE,
            MieleAppliance.ROBOT_VACUUM_CLEANER,
            MieleAppliance.WASHER_DRYER,
            MieleAppliance.STEAM_OVEN_COMBI,
            MieleAppliance.STEAM_OVEN_MICRO,
            MieleAppliance.DIALOG_OVEN,
            MieleAppliance.STEAM_OVEN_MK2,
        ),
        description=MieleSensorDescription(
            key="state_program_id",
            translation_key="program_id",
            device_class=SensorDeviceClass.ENUM,
            value_fn=lambda value: value.state_program_id,
        ),
    ),
    MieleSensorDefinition(
        types=(MieleAppliance.COFFEE_SYSTEM,),
        description=MieleSensorDescription(
            key="state_program_id",
            translation_key="program_id",
            device_class=SensorDeviceClass.ENUM,
            value_fn=lambda value: value.state_program_id,
            extra_attributes={
                ATTRIBUTE_PROFILE: _get_coffee_profile,
            },
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.WASHING_MACHINE,
            MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
            MieleAppliance.TUMBLE_DRYER,
            MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
            MieleAppliance.DISHWASHER,
            MieleAppliance.DISH_WARMER,
            MieleAppliance.OVEN,
            MieleAppliance.OVEN_MICROWAVE,
            MieleAppliance.STEAM_OVEN,
            MieleAppliance.MICROWAVE,
            MieleAppliance.COFFEE_SYSTEM,
            MieleAppliance.WASHER_DRYER,
            MieleAppliance.STEAM_OVEN_COMBI,
            MieleAppliance.STEAM_OVEN_MICRO,
            MieleAppliance.DIALOG_OVEN,
            MieleAppliance.STEAM_OVEN_MK2,
        ),
        description=MieleSensorDescription(
            key="state_program_phase",
            translation_key="program_phase",
            value_fn=lambda value: value.state_program_phase,
            device_class=SensorDeviceClass.ENUM,
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.WASHING_MACHINE,
            MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
            MieleAppliance.TUMBLE_DRYER,
            MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
            MieleAppliance.DISHWASHER,
            MieleAppliance.DISH_WARMER,
            MieleAppliance.OVEN,
            MieleAppliance.OVEN_MICROWAVE,
            MieleAppliance.STEAM_OVEN,
            MieleAppliance.MICROWAVE,
            MieleAppliance.ROBOT_VACUUM_CLEANER,
            MieleAppliance.WASHER_DRYER,
            MieleAppliance.STEAM_OVEN_COMBI,
            MieleAppliance.STEAM_OVEN_MICRO,
            MieleAppliance.DIALOG_OVEN,
            MieleAppliance.COFFEE_SYSTEM,
            MieleAppliance.STEAM_OVEN_MK2,
        ),
        description=MieleSensorDescription(
            key="state_program_type",
            translation_key="program_type",
            value_fn=lambda value: StateProgramType(value.state_program_type).name,
            entity_category=EntityCategory.DIAGNOSTIC,
            device_class=SensorDeviceClass.ENUM,
            options=sorted(set(StateProgramType.keys())),
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.WASHING_MACHINE,
            MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
            MieleAppliance.TUMBLE_DRYER,
            MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
            MieleAppliance.DISHWASHER,
            MieleAppliance.WASHER_DRYER,
        ),
        description=MieleSensorDescription(
            key="current_energy_consumption",
            translation_key="energy_consumption",
            value_fn=lambda value: value.current_energy_consumption,
            device_class=SensorDeviceClass.ENERGY,
            state_class=SensorStateClass.TOTAL_INCREASING,
            native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
            suggested_display_precision=1,
            entity_category=EntityCategory.DIAGNOSTIC,
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.WASHING_MACHINE,
            MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
            MieleAppliance.TUMBLE_DRYER,
            MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
            MieleAppliance.DISHWASHER,
            MieleAppliance.WASHER_DRYER,
        ),
        description=MieleSensorDescription(
            key="energy_forecast",
            translation_key="energy_forecast",
            value_fn=(
                lambda value: value.energy_forecast * 100
                if value.energy_forecast is not None
                else None
            ),
            native_unit_of_measurement=PERCENTAGE,
            entity_category=EntityCategory.DIAGNOSTIC,
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.WASHING_MACHINE,
            MieleAppliance.DISHWASHER,
            MieleAppliance.WASHER_DRYER,
        ),
        description=MieleSensorDescription(
            key="current_water_consumption",
            translation_key="water_consumption",
            value_fn=lambda value: value.current_water_consumption,
            device_class=SensorDeviceClass.WATER,
            state_class=SensorStateClass.TOTAL_INCREASING,
            native_unit_of_measurement=UnitOfVolume.LITERS,
            suggested_display_precision=0,
            entity_category=EntityCategory.DIAGNOSTIC,
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.WASHING_MACHINE,
            MieleAppliance.DISHWASHER,
            MieleAppliance.WASHER_DRYER,
        ),
        description=MieleSensorDescription(
            key="water_forecast",
            translation_key="water_forecast",
            value_fn=(
                lambda value: value.water_forecast * 100
                if value.water_forecast is not None
                else None
            ),
            native_unit_of_measurement=PERCENTAGE,
            entity_category=EntityCategory.DIAGNOSTIC,
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.WASHING_MACHINE,
            MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
            MieleAppliance.WASHER_DRYER,
        ),
        description=MieleSensorDescription(
            key="state_spinning_speed",
            translation_key="spin_speed",
            value_fn=lambda value: value.state_spinning_speed,
            native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
            entity_category=EntityCategory.DIAGNOSTIC,
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.WASHING_MACHINE,
            MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
            MieleAppliance.TUMBLE_DRYER,
            MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
            MieleAppliance.DISHWASHER,
            MieleAppliance.OVEN,
            MieleAppliance.OVEN_MICROWAVE,
            MieleAppliance.STEAM_OVEN,
            MieleAppliance.MICROWAVE,
            MieleAppliance.ROBOT_VACUUM_CLEANER,
            MieleAppliance.WASHER_DRYER,
            MieleAppliance.STEAM_OVEN_COMBI,
            MieleAppliance.STEAM_OVEN_MICRO,
            MieleAppliance.DIALOG_OVEN,
            MieleAppliance.STEAM_OVEN_MK2,
        ),
        description=MieleSensorDescription(
            key="state_remaining_time",
            translation_key="remaining_time",
            value_fn=lambda value: _convert_duration(value.state_remaining_time),
            end_value_fn=lambda last_value: 0,
            device_class=SensorDeviceClass.DURATION,
            native_unit_of_measurement=UnitOfTime.MINUTES,
            entity_category=EntityCategory.DIAGNOSTIC,
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.WASHING_MACHINE,
            MieleAppliance.TUMBLE_DRYER,
            MieleAppliance.DISHWASHER,
            MieleAppliance.OVEN,
            MieleAppliance.OVEN_MICROWAVE,
            MieleAppliance.STEAM_OVEN,
            MieleAppliance.MICROWAVE,
            MieleAppliance.WASHER_DRYER,
            MieleAppliance.STEAM_OVEN_COMBI,
            MieleAppliance.STEAM_OVEN_MICRO,
            MieleAppliance.DIALOG_OVEN,
            MieleAppliance.ROBOT_VACUUM_CLEANER,
            MieleAppliance.STEAM_OVEN_MK2,
        ),
        description=MieleSensorDescription(
            key="state_elapsed_time",
            translation_key="elapsed_time",
            value_fn=lambda value: _convert_duration(value.state_elapsed_time),
            end_value_fn=lambda last_value: last_value,
            device_class=SensorDeviceClass.DURATION,
            native_unit_of_measurement=UnitOfTime.MINUTES,
            entity_category=EntityCategory.DIAGNOSTIC,
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.WASHING_MACHINE,
            MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
            MieleAppliance.TUMBLE_DRYER,
            MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
            MieleAppliance.DISHWASHER,
            MieleAppliance.DISH_WARMER,
            MieleAppliance.OVEN,
            MieleAppliance.OVEN_MICROWAVE,
            MieleAppliance.STEAM_OVEN,
            MieleAppliance.MICROWAVE,
            MieleAppliance.WASHER_DRYER,
            MieleAppliance.STEAM_OVEN_COMBI,
            MieleAppliance.STEAM_OVEN_MICRO,
            MieleAppliance.DIALOG_OVEN,
            MieleAppliance.STEAM_OVEN_MK2,
        ),
        description=MieleSensorDescription(
            key="state_start_time",
            translation_key="start_time",
            value_fn=lambda value: _convert_duration(value.state_start_time),
            end_value_fn=lambda last_value: None,
            native_unit_of_measurement=UnitOfTime.MINUTES,
            device_class=SensorDeviceClass.DURATION,
            entity_category=EntityCategory.DIAGNOSTIC,
            suggested_display_precision=2,
            suggested_unit_of_measurement=UnitOfTime.HOURS,
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
            MieleAppliance.OVEN,
            MieleAppliance.OVEN_MICROWAVE,
            MieleAppliance.DISH_WARMER,
            MieleAppliance.STEAM_OVEN,
            MieleAppliance.MICROWAVE,
            MieleAppliance.FRIDGE,
            MieleAppliance.FREEZER,
            MieleAppliance.FRIDGE_FREEZER,
            MieleAppliance.STEAM_OVEN_COMBI,
            MieleAppliance.WINE_CABINET,
            MieleAppliance.WINE_CONDITIONING_UNIT,
            MieleAppliance.WINE_STORAGE_CONDITIONING_UNIT,
            MieleAppliance.STEAM_OVEN_MICRO,
            MieleAppliance.DIALOG_OVEN,
            MieleAppliance.WINE_CABINET_FREEZER,
            MieleAppliance.STEAM_OVEN_MK2,
        ),
        description=MieleSensorDescription(
            key="state_temperature_1",
            zone=1,
            device_class=SensorDeviceClass.TEMPERATURE,
            native_unit_of_measurement=UnitOfTemperature.CELSIUS,
            state_class=SensorStateClass.MEASUREMENT,
            value_fn=lambda value: _convert_temperature(value.state_temperatures, 0),
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.FRIDGE_FREEZER,
            MieleAppliance.WINE_CABINET,
            MieleAppliance.WINE_CONDITIONING_UNIT,
            MieleAppliance.WINE_STORAGE_CONDITIONING_UNIT,
            MieleAppliance.WINE_CABINET_FREEZER,
        ),
        description=MieleSensorDescription(
            key="state_temperature_2",
            zone=2,
            device_class=SensorDeviceClass.TEMPERATURE,
            translation_key="temperature_zone_2",
            native_unit_of_measurement=UnitOfTemperature.CELSIUS,
            state_class=SensorStateClass.MEASUREMENT,
            value_fn=lambda value: _convert_temperature(value.state_temperatures, 1),
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.WINE_CABINET,
            MieleAppliance.WINE_CONDITIONING_UNIT,
            MieleAppliance.WINE_STORAGE_CONDITIONING_UNIT,
            MieleAppliance.WINE_CABINET_FREEZER,
        ),
        description=MieleSensorDescription(
            key="state_temperature_3",
            zone=3,
            device_class=SensorDeviceClass.TEMPERATURE,
            translation_key="temperature_zone_3",
            native_unit_of_measurement=UnitOfTemperature.CELSIUS,
            state_class=SensorStateClass.MEASUREMENT,
            value_fn=lambda value: _convert_temperature(value.state_temperatures, 2),
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.OVEN,
            MieleAppliance.OVEN_MICROWAVE,
            MieleAppliance.STEAM_OVEN_COMBI,
            MieleAppliance.STEAM_OVEN_MK2,
        ),
        description=MieleSensorDescription(
            key="state_core_target_temperature",
            translation_key="core_target_temperature",
            device_class=SensorDeviceClass.TEMPERATURE,
            native_unit_of_measurement=UnitOfTemperature.CELSIUS,
            state_class=SensorStateClass.MEASUREMENT,
            value_fn=lambda value: _convert_temperature(
                value.state_core_target_temperature, 0
            ),
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.WASHING_MACHINE,
            MieleAppliance.WASHER_DRYER,
            MieleAppliance.OVEN,
            MieleAppliance.OVEN_MICROWAVE,
            MieleAppliance.STEAM_OVEN_MICRO,
            MieleAppliance.STEAM_OVEN_COMBI,
            MieleAppliance.STEAM_OVEN_MK2,
        ),
        description=MieleSensorDescription(
            key="state_target_temperature",
            translation_key="target_temperature",
            zone=1,
            device_class=SensorDeviceClass.TEMPERATURE,
            native_unit_of_measurement=UnitOfTemperature.CELSIUS,
            state_class=SensorStateClass.MEASUREMENT,
            value_fn=lambda value: _convert_temperature(
                value.state_target_temperature, 0
            ),
        ),
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.OVEN,
            MieleAppliance.OVEN_MICROWAVE,
            MieleAppliance.STEAM_OVEN_COMBI,
        ),
        description=MieleSensorDescription(
            key="state_core_temperature",
            translation_key="core_temperature",
            device_class=SensorDeviceClass.TEMPERATURE,
            native_unit_of_measurement=UnitOfTemperature.CELSIUS,
            state_class=SensorStateClass.MEASUREMENT,
            value_fn=lambda value: _convert_temperature(
                value.state_core_temperature, 0
            ),
        ),
    ),
    *(
        MieleSensorDefinition(
            types=(
                MieleAppliance.HOB_HIGHLIGHT,
                MieleAppliance.HOB_INDUCT_EXTR,
                MieleAppliance.HOB_INDUCTION,
            ),
            description=MieleSensorDescription(
                key="state_plate_step",
                translation_key="plate",
                translation_placeholders={"plate_no": str(i)},
                zone=i,
                device_class=SensorDeviceClass.ENUM,
                options=sorted(PlatePowerStep.keys()),
                value_fn=lambda value: None,
                unique_id_fn=lambda device_id,
                description: f"{device_id}-{description.key}-{description.zone}",
            ),
        )
        for i in range(1, 7)
    ),
    MieleSensorDefinition(
        types=(
            MieleAppliance.WASHER_DRYER,
            MieleAppliance.TUMBLE_DRYER,
            MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
        ),
        description=MieleSensorDescription(
            key="state_drying_step",
            translation_key="drying_step",
            value_fn=lambda value: StateDryingStep(
                cast(int, value.state_drying_step)
            ).name,
            entity_category=EntityCategory.DIAGNOSTIC,
            device_class=SensorDeviceClass.ENUM,
            options=sorted(StateDryingStep.keys()),
        ),
    ),
    MieleSensorDefinition(
        types=(MieleAppliance.ROBOT_VACUUM_CLEANER,),
        description=MieleSensorDescription(
            key="state_battery",
            value_fn=lambda value: value.state_battery_level,
            native_unit_of_measurement=PERCENTAGE,
            entity_category=EntityCategory.DIAGNOSTIC,
            device_class=SensorDeviceClass.BATTERY,
        ),
    ),
)


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: MieleConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up the sensor platform."""
    coordinator = config_entry.runtime_data
    added_devices: set[str] = set()  # device_id
    added_entities: set[str] = set()  # unique_id

    def _get_entity_class(definition: MieleSensorDefinition) -> type[MieleSensor]:
        """Get the entity class for the sensor."""
        return {
            "state_status": MieleStatusSensor,
            "state_program_id": MieleProgramIdSensor,
            "state_program_phase": MielePhaseSensor,
            "state_plate_step": MielePlateSensor,
            "state_elapsed_time": MieleTimeSensor,
            "state_remaining_time": MieleTimeSensor,
            "state_start_time": MieleTimeSensor,
            "current_energy_consumption": MieleConsumptionSensor,
            "current_water_consumption": MieleConsumptionSensor,
        }.get(definition.description.key, MieleSensor)

    def _is_entity_registered(unique_id: str) -> bool:
        """Check if the entity is already registered."""
        entity_registry = er.async_get(hass)
        return any(
            entry.platform == DOMAIN and entry.unique_id == unique_id
            for entry in entity_registry.entities.values()
        )

    def _is_sensor_enabled(
        definition: MieleSensorDefinition,
        device: MieleDevice,
        unique_id: str,
    ) -> bool:
        """Check if the sensor is enabled."""
        if (
            definition.description.device_class == SensorDeviceClass.TEMPERATURE
            and definition.description.value_fn(device) is None
            and definition.description.zone != 1
        ):
            # all appliances supporting temperature have at least zone 1, for other zones
            # don't create entity if API signals that datapoint is disabled, unless the sensor
            # already appeared in the past (= it provided a valid value)
            return _is_entity_registered(unique_id)
        if (
            definition.description.key == "state_plate_step"
            and definition.description.zone is not None
            and definition.description.zone > _get_plate_count(device.tech_type)
        ):
            # don't create plate entity if not expected by the appliance tech type
            return False
        return True

    def _async_add_devices() -> None:
        nonlocal added_devices, added_entities
        entities: list = []
        entity_class: type[MieleSensor]
        new_devices_set, current_devices = coordinator.async_add_devices(added_devices)
        added_devices = current_devices

        for device_id, device in coordinator.data.devices.items():
            for definition in SENSOR_TYPES:
                # device is not supported, skip
                if device.device_type not in definition.types:
                    continue

                entity_class = _get_entity_class(definition)
                unique_id = (
                    definition.description.unique_id_fn(
                        device_id, definition.description
                    )
                    if definition.description.unique_id_fn is not None
                    else MieleEntity.get_unique_id(device_id, definition.description)
                )

                # entity was already added, skip
                if device_id not in new_devices_set and unique_id in added_entities:
                    continue

                # sensors is not enabled, skip
                if not _is_sensor_enabled(definition, device, unique_id):
                    continue

                added_entities.add(unique_id)
                entities.append(
                    entity_class(coordinator, device_id, definition.description)
                )
        async_add_entities(entities)

    config_entry.async_on_unload(coordinator.async_add_listener(_async_add_devices))
    _async_add_devices()


APPLIANCE_ICONS = {
    MieleAppliance.WASHING_MACHINE: "mdi:washing-machine",
    MieleAppliance.TUMBLE_DRYER: "mdi:tumble-dryer",
    MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL: "mdi:tumble-dryer",
    MieleAppliance.DISHWASHER: "mdi:dishwasher",
    MieleAppliance.OVEN: "mdi:chef-hat",
    MieleAppliance.OVEN_MICROWAVE: "mdi:chef-hat",
    MieleAppliance.HOB_HIGHLIGHT: "mdi:pot-steam-outline",
    MieleAppliance.STEAM_OVEN: "mdi:chef-hat",
    MieleAppliance.MICROWAVE: "mdi:microwave",
    MieleAppliance.COFFEE_SYSTEM: "mdi:coffee-maker",
    MieleAppliance.HOOD: "mdi:turbine",
    MieleAppliance.FRIDGE: "mdi:fridge-industrial-outline",
    MieleAppliance.FREEZER: "mdi:fridge-industrial-outline",
    MieleAppliance.FRIDGE_FREEZER: "mdi:fridge-outline",
    MieleAppliance.ROBOT_VACUUM_CLEANER: "mdi:robot-vacuum",
    MieleAppliance.WASHER_DRYER: "mdi:washing-machine",
    MieleAppliance.DISH_WARMER: "mdi:heat-wave",
    MieleAppliance.HOB_INDUCTION: "mdi:pot-steam-outline",
    MieleAppliance.STEAM_OVEN_COMBI: "mdi:chef-hat",
    MieleAppliance.WINE_CABINET: "mdi:glass-wine",
    MieleAppliance.WINE_CONDITIONING_UNIT: "mdi:glass-wine",
    MieleAppliance.WINE_STORAGE_CONDITIONING_UNIT: "mdi:glass-wine",
    MieleAppliance.STEAM_OVEN_MICRO: "mdi:chef-hat",
    MieleAppliance.DIALOG_OVEN: "mdi:chef-hat",
    MieleAppliance.WINE_CABINET_FREEZER: "mdi:glass-wine",
    MieleAppliance.HOB_INDUCT_EXTR: "mdi:pot-steam-outline",
}


class MieleSensor(MieleEntity, SensorEntity):
    """Representation of a Sensor."""

    entity_description: MieleSensorDescription

    def __init__(
        self,
        coordinator: MieleDataUpdateCoordinator,
        device_id: str,
        description: MieleSensorDescription,
    ) -> None:
        """Initialize the sensor."""
        super().__init__(coordinator, device_id, description)
        if description.unique_id_fn is not None:
            self._attr_unique_id = description.unique_id_fn(device_id, description)

    @property
    def native_value(self) -> StateType:
        """Return the state of the sensor."""
        return self.entity_description.value_fn(self.device)

    @property
    def extra_state_attributes(self) -> Mapping[str, Any] | None:
        """Return extra_state_attributes."""
        if self.entity_description.extra_attributes is None:
            return None
        attr = {}
        for key, value in self.entity_description.extra_attributes.items():
            attr[key] = value(self.device)
        return attr


class MieleRestorableSensor(MieleSensor, RestoreSensor):
    """Representation of a Sensor whose internal state can be restored."""

    _last_value: StateType

    def __init__(
        self,
        coordinator: MieleDataUpdateCoordinator,
        device_id: str,
        description: MieleSensorDescription,
    ) -> None:
        """Initialize the sensor."""
        super().__init__(coordinator, device_id, description)
        self._last_value = None

    async def async_added_to_hass(self) -> None:
        """When entity is added to hass."""
        await super().async_added_to_hass()

        # recover last value from cache when adding entity
        last_value = await self.async_get_last_state()
        if last_value and last_value.state != STATE_UNKNOWN:
            self._last_value = last_value.state

    @property
    def native_value(self) -> StateType:
        """Return the state of the sensor."""
        return self._last_value

    def _update_last_value(self) -> None:
        """Update the last value of the sensor."""
        self._last_value = self.entity_description.value_fn(self.device)

    @callback
    def _handle_coordinator_update(self) -> None:
        """Handle updated data from the coordinator."""
        self._update_last_value()
        super()._handle_coordinator_update()


class MielePlateSensor(MieleSensor):
    """Representation of a Sensor."""

    entity_description: MieleSensorDescription

    @property
    def native_value(self) -> StateType:
        """Return the state of the plate sensor."""
        # state_plate_step is [] if all zones are off

        return (
            PlatePowerStep(
                cast(
                    int,
                    self.device.state_plate_step[
                        cast(int, self.entity_description.zone) - 1
                    ].value_raw,
                )
            ).name
            if self.device.state_plate_step
            else PlatePowerStep.plate_step_0.name
        )


class MieleStatusSensor(MieleSensor):
    """Representation of the status sensor."""

    def __init__(
        self,
        coordinator: MieleDataUpdateCoordinator,
        device_id: str,
        description: MieleSensorDescription,
    ) -> None:
        """Initialize the sensor."""
        super().__init__(coordinator, device_id, description)
        self._attr_name = None
        self._attr_icon = APPLIANCE_ICONS.get(
            MieleAppliance(self.device.device_type),
            "mdi:state-machine",
        )

    @property
    def native_value(self) -> StateType:
        """Return the state of the sensor."""
        return STATE_STATUS_TAGS.get(StateStatus(self.device.state_status))

    @property
    def available(self) -> bool:
        """Return the availability of the entity."""
        # This sensor should always be available
        return True


# Some phases have names that are not valid python identifiers, so we need to translate
# them in order to avoid breaking changes
PROGRAM_PHASE_TRANSLATION = {
    "second_espresso": "2nd_espresso",
    "second_grinding": "2nd_grinding",
    "second_pre_brewing": "2nd_pre_brewing",
}


class MielePhaseSensor(MieleSensor):
    """Representation of the program phase sensor."""

    @property
    def native_value(self) -> StateType:
        """Return the state of the phase sensor."""
        program_phase = PROGRAM_PHASE[self.device.device_type](
            self.device.state_program_phase
        ).name

        return (
            PROGRAM_PHASE_TRANSLATION.get(program_phase, program_phase)
            if program_phase is not None
            else None
        )

    @property
    def options(self) -> list[str]:
        """Return the options list for the actual device type."""
        phases = PROGRAM_PHASE[self.device.device_type].keys()
        return sorted([PROGRAM_PHASE_TRANSLATION.get(phase, phase) for phase in phases])


class MieleProgramIdSensor(MieleSensor):
    """Representation of the program id sensor."""

    _unrecorded_attributes = frozenset({ATTRIBUTE_PROFILE})

    @property
    def native_value(self) -> StateType:
        """Return the state of the sensor."""
        ret_val = STATE_PROGRAM_ID.get(self.device.device_type, {}).get(
            self.device.state_program_id
        )
        if ret_val is None:
            _LOGGER.debug(
                "Unknown program id: %s on device type: %s",
                self.device.state_program_id,
                self.device.device_type,
            )
        return ret_val

    @property
    def options(self) -> list[str]:
        """Return the options list for the actual device type."""
        return sorted(set(STATE_PROGRAM_ID.get(self.device.device_type, {}).values()))


class MieleTimeSensor(MieleRestorableSensor):
    """Representation of time sensors keeping state from cache."""

    def _update_last_value(self) -> None:
        """Update the last value of the sensor."""

        current_value = self.entity_description.value_fn(self.device)
        current_status = StateStatus(self.device.state_status)

        # report end-specific value when program ends (some devices are immediately reporting 0...)
        if (
            current_status == StateStatus.PROGRAM_ENDED
            and self.entity_description.end_value_fn is not None
        ):
            self._last_value = self.entity_description.end_value_fn(self._last_value)

        # keep value when program ends if no function is specified
        elif current_status == StateStatus.PROGRAM_ENDED:
            pass

        # force unknown when appliance is not working (some devices are keeping last value until a new cycle starts)
        elif current_status in (StateStatus.OFF, StateStatus.ON, StateStatus.IDLE):
            self._last_value = None

        # otherwise, cache value and return it
        else:
            self._last_value = current_value


class MieleConsumptionSensor(MieleRestorableSensor):
    """Representation of consumption sensors keeping state from cache."""

    _is_reporting: bool = False

    def _update_last_value(self) -> None:
        """Update the last value of the sensor."""
        current_value = self.entity_description.value_fn(self.device)
        current_status = StateStatus(self.device.state_status)
        last_value = (
            float(cast(str, self._last_value))
            if self._last_value is not None and self._last_value != STATE_UNKNOWN
            else 0
        )

        # force unknown when appliance is not able to report consumption
        if current_status in (
            StateStatus.ON,
            StateStatus.OFF,
            StateStatus.PROGRAMMED,
            StateStatus.WAITING_TO_START,
            StateStatus.IDLE,
            StateStatus.SERVICE,
        ):
            self._is_reporting = False
            self._last_value = None

        # appliance might report the last value for consumption of previous cycle and it will report 0
        # only after a while, so it is necessary to force 0 until we see the 0 value coming from API, unless
        # we already saw a valid value in this cycle from cache
        elif (
            current_status in (StateStatus.IN_USE, StateStatus.PAUSE)
            and not self._is_reporting
            and last_value > 0
        ):
            self._last_value = current_value
            self._is_reporting = True

        elif (
            current_status in (StateStatus.IN_USE, StateStatus.PAUSE)
            and not self._is_reporting
            and current_value is not None
            and cast(int, current_value) > 0
        ):
            self._last_value = 0

        # keep value when program ends
        elif current_status == StateStatus.PROGRAM_ENDED:
            pass

        else:
            self._last_value = current_value
            self._is_reporting = True
